import ipaddress
import json
import os
import shutil

from django.conf import settings
from django.db import models, transaction
from django.utils.translation import gettext_lazy as _, gettext
from django.core.exceptions import ValidationError

from ceph.site_parameters import nextcloud_databases_json, email_json
from utils.iprestrict import convert_iprange
from utils.oss import HarborObject
from utils.validators import root_http_url_validator


class CephCluster(models.Model):
    """
    ceph 集群配置信息
    """
    id = models.AutoField(primary_key=True)
    name = models.CharField(verbose_name=_('存储池名称'), max_length=50, unique=True, blank=False)
    cluster_name = models.CharField(verbose_name=_('CLUSTER_NAME配置名称'), max_length=50, blank=False)
    user_name = models.CharField(verbose_name=_('用户名称(USER_NAME)'), max_length=50, blank=False)
    disable_choice = models.BooleanField(verbose_name=_('新数据写入启用'), default=False,
                                         help_text="默认false, 如果为true则该ceph可写入数据")
    pool_names = models.JSONField(verbose_name=_('POOL存储池名称'), blank=False, default=list,
                                  help_text='配置标准：[\"池名1\"]')

    config = models.TextField(verbose_name=_('ceph集群配置文本'), default='')
    config_file = models.CharField(max_length=200, editable=False, blank=True, verbose_name=_('配置文件保存路径'),
                                   help_text=_("点击保存，配置文本会存储到这个文件, 此字段自动填充"))
    keyring = models.TextField(verbose_name=_('ceph集群keyring文本'))
    keyring_file = models.CharField(max_length=200, editable=False, blank=True, verbose_name=_('keyring文件保存路径'),
                                    help_text=_("点击保存，keyring文本会存储到这个文件, 此字段自动填充"))
    modified_time = models.DateTimeField(auto_now=True, verbose_name=_('修改时间'))
    # 优先存储值
    priority_stored_value = models.IntegerField(
        verbose_name=_('优先值'), default=0,
        help_text=_('根据排序，选择最小值为默认的存储池,请合理填入（-2147483648 到 2147483647）'))
    remarks = models.CharField(verbose_name=_('备注'), max_length=255, blank=True, default='')

    class Meta:
        ordering = ('id',)
        verbose_name = _('CEPH存储集群')
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

    def get_config_file(self):
        """
        ceph配置文件路径
        :return: str
        """
        if not self.config_file:
            self.save_config_to_file()

        return self.config_file

    def get_keyring_file(self):
        """
        ceph keyring文件路径
        :return: str
        """
        if not self.keyring_file:
            self.save_config_to_file()

        return self.keyring_file

    def save_config_to_file(self, path=None):
        """
        ceph的配置内容保存到配置文件

        :return:
            True    # success
            False   # failed
        """
        if not path:
            path = os.path.join(settings.BASE_DIR, 'data/ceph/conf/')
        else:
            path = os.path.join(settings.BASE_DIR, path)

        self.config_file = os.path.join(path, f'{self.id}.conf')
        self.keyring_file = os.path.join(path, f'{self.id}.keyring')

        try:
            # 目录路径不存在存在则创建
            os.makedirs(path, exist_ok=True)

            with open(self.config_file, 'w') as f:
                config = self.config.replace('\r\n', '\n')  # Windows
                self.config = config.replace('\r', '\n')  # MacOS
                f.write(self.config + '\n')  # 最后留空行

            with open(self.keyring_file, 'w') as f:
                keyring = self.keyring.replace('\r\n', '\n')
                self.keyring = keyring.replace('\r', '\n')
                f.write(self.keyring + '\n')
        except Exception:
            return False

        return True

    def save(self, *args, **kwargs):
        old_config_file = self.config_file
        old_keyring_file = self.keyring_file
        with transaction.atomic():
            super().save(*args, **kwargs)
            self.save_config_to_file()
            if self.config_file != old_config_file or self.keyring_file != old_keyring_file:
                super().save(update_fields=['config_file', 'keyring_file'])

    def delete(self, *args, **kwargs):
        # 删除配置文件
        os.remove(self.config_file)
        os.remove(self.keyring_file)
        super().delete(*args, **kwargs)

    def clean(self):
        """
        校验模型中字段内容
        """
        if self.id:  # change
            if CephCluster.objects.filter(
                    priority_stored_value=self.priority_stored_value).exclude(id=self.id).exists():
                raise ValidationError({'priority_stored_value': _('优先权数值不能与其他ceph存储池的值相同。')})
        else:  # add new
            if CephCluster.objects.filter(priority_stored_value=self.priority_stored_value).exists():
                raise ValidationError({'priority_stored_value': _('优先权数值不能与其他ceph存储池的值相同。')})

        if not isinstance(self.pool_names, list):
            raise ValidationError({'pool_names': _('字段类型必须是一个json格式的数组。')})

        if not self.pool_names:
            raise ValidationError({'pool_names': _('该字段不不能为空。')})

        if not all(self.pool_names):
            raise ValidationError({'pool_names': _('存储池不能有为空的情况。')})

        # 备份路径用于测试ceph连接
        path = f'data/ceph/{self.id}test/conf'
        self.save_config_to_file(path=path)

        ho = HarborObject(pool_name='', obj_id='', obj_size=2, cluster_name=self.cluster_name,
                          user_name=self.user_name, conf_file=self.config_file, keyring_file=self.keyring_file,
                          alise_cluster=self.id)
        try:
            list_pool_cluster = ho.rados.get_cluster().list_pools()
            for pool_name in self.pool_names:
                if pool_name not in list_pool_cluster:
                    raise ValidationError({'pool_names': _(f'集群中该存储池 {pool_name} 不存在。')})
        except ValidationError as exc:
            raise exc
        except Exception as e:
            # [errno 22] RADOS invalid argument (error calling conf_read_file)
            # [errno 22] RADOS invalid argument (error calling conf_read_file)
            # [errno 5] RADOS I/O error (error connecting to the cluster)
            # [errno 1] RADOS permission error (error connecting to the cluster) user_name
            # [errno 13] RADOS permission denied (error connecting to the cluster) user_name
            errno = getattr(e, 'errno', None)
            if not errno:
                raise ValidationError({_('无法连接ceph集群，请重新查看填写的配置。')})
            elif errno == 22:
                raise ValidationError({'config': _('配置文件填写有误： RADOS invalid argument (error calling '
                                                   'conf_read_file)。'),
                                       'keyring': _('配置文件填写有误：RADOS invalid argument (error calling '
                                                    'conf_read_file)。')})
            elif errno == 1 or errno == 13:
                raise ValidationError({'user_name': _('该字段填写有误：RADOS permission error/denied (error connecting to '
                                                      'the cluster)。')})
            output = _(f'请查看ceph集群是否正常使用，报错：{e}。')
            raise ValidationError(output)
        finally:
            ho.rados.close_cluster_connect()
            # 删除测试的备份路径
            shutil.rmtree(f'data/ceph/{self.id}test')

    def get_pool_name(self):
        return self.pool_names[0]


class GlobalConfig(models.Model):
    """全局配置表"""

    class ConfigName(models.TextChoices):
        SITE_NAME = 'site_name', _('站点名称')
        POWER_BY = 'power_by', _('技术支持')
        FTP_CERT_FILE_PATH = 'ftp_cert_file_path', _('FTP认证文件路径')
        FTP_LOG_PATH = 'ftp_log_path', _('FTP日志文件路径')
        FTP_DOMAIN_NAME = 'ftp_domain_name', _('ftp域名')
        AAI_JWT_VERIFYING_KEY = 'passport_jwt', _('AAI JWT认证公钥')
        RESOURCE_ADMINISTRATOR = 'resource_administrator', _('资源管理员')
        EMAIL_CONFIG = 'email_config', _('邮件配置信息')
        USER_REGISTER = 'user_register', _('用户注册功能开关')
        S3_API_ENABLE = 's3_api_enable', _('启用s3服务')
        S3_SERVICE_URL = 's3_service_url', _('s3域名')
        NEXTCLOUD_S3_SERVICE_URL = 'nextcloud_s3_service_url', _('nextcloud s3 域名地址')
        NEXTCLOUD_ADMIN_ACCOUNT = 'nextcloud_admin_account', _('nextcloud管理员账户和密码用于创建普通用户信息')
        NEXTCLOUD_DATABASE = 'nextcloud_database', _('nextcloud数据库链接基本信息')
        NEXTCLOUD_SERVICE_URL = 'nextcloud_service_url', _('nextcloud 使用域名')
        LOCAL_ENDPOINT = 'local_endpoint', _('本服务地址')
        YUNKUN_AUTH_NAME = 'yunkun_auth_name', _('云坤身份认证服务名称')
        YUNKUN_AUTH_ENDPOINT = 'yunkun_auth_endpoint', _('云坤身份认证服务地址')

    # 配置的默认值，自动创建配置参数记录时填充的默认值
    value_defaults = {
        ConfigName.SITE_NAME.value: 'iHarbor',
        ConfigName.POWER_BY.value: 'https://gitee.com/cstcloud-cnic/iharbor',
        ConfigName.FTP_CERT_FILE_PATH.value: '/etc/nginx/conf.d/ssl/iharbor_ftp_cstcloud.cn.pem',
        ConfigName.AAI_JWT_VERIFYING_KEY.value: '',
        ConfigName.FTP_LOG_PATH.value: '/var/log/iharbor/ftp.log',
        ConfigName.RESOURCE_ADMINISTRATOR.value: '',
        ConfigName.S3_SERVICE_URL.value: 'localhost:8002',
        ConfigName.EMAIL_CONFIG.value: {
            'content': json.dumps(email_json, indent=1),
            'remark': '邮件配置信息。说明：通过api方式发送邮件(ZKApiEmail)或自定义邮箱用户信息发送邮件(customEmail)，由useSendEmailType参数判定选择哪种方式发送(只能填写customEmail或ZKApiEmail)，如果选择api方式发送邮件前提需要到api服务中认证自己的IP地址, 并指定服务地址(ZKSERVERURL)'
        },
        ConfigName.FTP_DOMAIN_NAME.value: {'value': '', 'remark': '多个域名用换行分开'},
        ConfigName.USER_REGISTER.value: {'content': 'false', 'remark': '用户注册功能开关(true/false默认:false - 关)'},
        ConfigName.S3_API_ENABLE.value: {'content': 'true', 'remark': '启用s3服务'},
        ConfigName.NEXTCLOUD_DATABASE.value: {'content': json.dumps(nextcloud_databases_json, indent=1),
                                              'remark': 'nextCloud数据库连接基本信息'},
        ConfigName.NEXTCLOUD_S3_SERVICE_URL.value: {'content': 'x.x.x.x', 'remark': 'nextCloud s3域名, 多个域名使用换行符分开'},
        ConfigName.NEXTCLOUD_ADMIN_ACCOUNT.value: {'content': 'xxx:xxx',
                                                   'remark': 'nextCloud 管理员账户和密码用于创建普通用户信息,实例：name:pwd'},
        ConfigName.NEXTCLOUD_SERVICE_URL.value: {'content': 'xxxdisk.cstcloud.cn\nhttp://127.0.0.1:8025',
                                                 'remark': 'nextCloud 使用域名, 多个域名使用换行分开，示例：公网地址 "换行" 内网地址(地址需要http/https前缀), 通过换行符区分'},
        ConfigName.LOCAL_ENDPOINT.value: {'content': 'http://localhost:8001', 'remark': '本地服务地址（http(s)://xxxx）， 多个使用换行分开'},
        ConfigName.YUNKUN_AUTH_NAME.value: {'content': '中国科技云身份认证联盟(CSTCLOUD AAI)',
                                            'remark': '云坤身份认证服务名称'},
        ConfigName.YUNKUN_AUTH_ENDPOINT.value: {'content': 'https://zhongkun.cstcloud.cn',
                                                'remark': '云坤身份认证服务地址（http(s)://xxx）'}

    }

    id = models.AutoField(primary_key=True, verbose_name=_('ID'))
    name = models.CharField(verbose_name=_('变量'), max_length=255, choices=ConfigName.choices)
    content = models.TextField(verbose_name=_('值'), default='', null=True, blank=True)
    remark = models.CharField(verbose_name=_('备注信息'), max_length=255, default='', blank=True)
    create_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    modif_time = models.DateTimeField(verbose_name=_('修改时间'), auto_now=True)

    class Meta:
        db_table = 'site_global_config'
        ordering = ['-id']
        verbose_name = _('站点参数')
        verbose_name_plural = verbose_name

    def __str__(self):
        return f'GlobalConfig<{self.name}>'

    def clean(self):

        if self.content and self.name == self.ConfigName.RESOURCE_ADMINISTRATOR.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })

        if self.name == self.ConfigName.S3_SERVICE_URL.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })
            elif not self.content:
                raise ValidationError({'content': _('不允许为空')})

        if self.content and self.name == self.ConfigName.LOCAL_ENDPOINT.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })
            items = self.parse_lines(self.content)
            for item in items:
                try:
                    root_http_url_validator(item)
                except ValidationError as exc:
                    raise ValidationError(message={
                        'content': gettext(
                            '"{value}"不是一个有效的根网址，正确格式为“https://example.com”，多个网址请换行填写。'
                        ).format(value=item)})

            self.content = '\n'.join(items)

        if self.content and self.name == self.ConfigName.NEXTCLOUD_SERVICE_URL.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })

        if self.content and self.name == self.ConfigName.S3_SERVICE_URL.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })

        if self.content and self.name == self.ConfigName.NEXTCLOUD_S3_SERVICE_URL.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })

        if self.content and self.name == self.ConfigName.FTP_DOMAIN_NAME.value:
            if ',' in self.content or '，' in self.content:
                raise ValidationError(message={
                    'content': gettext(f'{self.content} 使用换行区分多条信息')
                })

    @staticmethod
    def parse_lines(text: str) -> list[str]:
        """
        解析字符串，按每行转成列表
        只返回移除每行前后的空格内容有效的行
        """
        names = text.splitlines()
        return [name.strip() for name in names if name and name.strip()]


class ApiAllowIP(models.Model):
    """全局配置表-管理员IP白名单：配置 nginx 允许的IP"""
    id = models.BigAutoField(primary_key=True)
    ip_value = models.CharField(
        verbose_name=_('IP'), max_length=100, help_text='192.168.1.1、 192.168.1.1/24、192.168.1.66 - 192.168.1.100')
    remark = models.CharField(verbose_name=_('备注'), max_length=255, blank=True, default='')
    creation_time = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
    update_time = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)

    class Meta:
        db_table = 'app_global_parameter_apiallowip'  # 后续 app 更名为 app_global_parameter
        ordering = ['-creation_time']
        verbose_name = _('IP访问白名单')
        verbose_name_plural = verbose_name

    def __str__(self):
        return f'{self.id}({self.ip_value})'

    def clean(self):
        try:
            subnet = convert_iprange(self.ip_value)
        except Exception as exc:
            raise ValidationError({'ip_value': str(exc)})

        if isinstance(subnet, ipaddress.IPv4Network):
            self.ip_value = str(subnet)
